package com.aiyi.server.manager.nginx.bean;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.aiyi.server.manager.nginx.bean.result.Result;
import com.aiyi.server.manager.nginx.common.CMDUtil;
import com.aiyi.server.manager.nginx.common.CommonFields;
import com.aiyi.server.manager.nginx.core.thread.SystemProcessManager;
import com.aiyi.server.manager.nginx.exception.NginxServiceManagerException;
import com.github.odiszapc.nginxparser.NgxConfig;
import com.github.odiszapc.nginxparser.NgxDumper;

import oshi.SystemInfo;
import oshi.software.os.OSFileStore;

public class NginxService {

	private static final Logger logger = LoggerFactory.getLogger(NginxService.class);

	private static String searchPath = "/www/server;/usr/local;/Program Files;/Program Files (x64);/Program Files (x86);/BtSoft";

	private static Map<String, NginxService> nginxServiceMap;

	private String nginxConfigPath;

	private String nginxPath;

	private String encoding;
	
	private ServiceManager serviceManager; 

	public int getId() {
		return nginxPath.hashCode();
	}
	
	/**
	 * @return the nginxConfigPath
	 */
	public String getNginxConfigPath() {
		return nginxConfigPath;
	}

	/**
	 * @return the nginxPath
	 */
	public String getNginxPath() {
		return nginxPath;
	}

	/**
	 * @return the encoding
	 */
	public String getEncoding() {
		return encoding;
	}

	/**
	 * @param encoding the encoding to set
	 */
	public void setEncoding(String encoding) {
		this.encoding = encoding;
	}

	private NginxService(String nginxPath, String nginxConfitPath) {
		this.nginxPath = nginxPath;
		
		if(nginxConfitPath==null) {
			// 未指定配置文件路径
			File file = new File(nginxPath+File.separator+"conf"+File.separator+"nginx.conf");
			if (file.exists()) {
				this.nginxConfigPath = file.getAbsolutePath();
			}
		} else {
			// 指定了配置文件路径
			this.nginxConfigPath = nginxConfitPath;
		}
		
		String[] encodingArray = new String[]{System.getProperty("file.encoding"),"GBK","UTF-8"};
		
		for(String encodeing1:encodingArray) {
			
			String conf = null;
			try( BufferedReader br = new BufferedReader( new InputStreamReader(new FileInputStream(nginxConfigPath), encodeing1 ))){
				StringBuilder sb = new StringBuilder();

				String line;

				while(( line = br.readLine()) != null ) {
					sb.append( line );
		
					sb.append("\r\n");

				}
				conf = sb.toString();
			} catch (Exception e) {
				e.printStackTrace();
			}
			
			if(isMessyCode(conf)) {
				logger.warn("警告：编码格式（"+encodeing1+"）有乱码!!!");
				this.encoding = encodeing1;
			} else {
				logger.info("信息：编码格式（"+encodeing1+"）正常!");
				this.encoding = encodeing1;
				break;
			}
		}
		
		this.serviceManager = ServiceManager.getInstance(nginxPath);
	}

	public static List<NginxService> getNginxServerList() {
		List<NginxService> nginxServices;

		if (nginxServiceMap == null) {
			nginxServiceMap = new HashMap<String, NginxService>();
			Set<String> pathSet = new HashSet<String>();
			for(String path:searchPath.split(";")) {
				File file = new File(path);
				if(file.exists()) {
					pathSet.add(file.getAbsolutePath());
					logger.debug("调试1：");
				} else {
					SystemInfo systemInfo = new oshi.SystemInfo();
					OSFileStore[] fsArray = systemInfo.getOperatingSystem().getFileSystem().getFileStores();
					for(OSFileStore fileStore:fsArray) {
						file = new File(fileStore.getMount()+path);
						if(file.exists()) {
							logger.debug("调试2：");
							pathSet.add(file.getAbsolutePath());
						} else {
							logger.debug("调试3：");
						}
					}
				}
			}
			for(String path:pathSet) {
				File file = new File(path);
				if(file.exists()&&file.isDirectory()){
					 File[] files = file.listFiles(new FileFilter() {
						
						@Override
						public boolean accept(File pathname) {
							boolean result = false;
							
							// 文件夹以名 nginx 开始
							if(pathname.isDirectory() && pathname.getName().toLowerCase().startsWith("nginx")) {
								// 子文件夹包含 conf/nginx.conf
								File confFile = new File(pathname.getAbsoluteFile()+File.separator+"conf"+File.separator+"nginx.conf");
								if(confFile.exists()) {
									result = true;
								}
									
							}
							return result;
						}
					});
					for(File subFile:files) {
						NginxService nginxService = new NginxService(subFile.getAbsolutePath(), null);
						nginxServiceMap.put(subFile.getAbsolutePath(), nginxService);
						logger.debug("调试3："+subFile.getAbsolutePath());
					}
				}
				logger.debug("调试4："+path);
			}
		}
		nginxServices = new ArrayList<NginxService>(nginxServiceMap.values());

		return nginxServices;
	}

	/**
	 * 服务是否启动
	 *
	 * @return : boolean
	 * @Description :
	 * @Creation Date : 2018年2月1日 下午4:59:37
	 * @Author : 郭胜凯
	 */
	public boolean isStart() {
		boolean result = false;
		
		// 获取当前服务最后的进程id存放位置，再获取最后进程id
		Long pid = getPid();
		if(pid!=null) {
			String nginxName = CommonFields.NGINX;
			result = SystemProcessManager.isExist(nginxName, pid);
		}
		
		return result;
	}

	private Long getPid() {
		Long pid = null;
		
		File file = new File(nginxPath+File.separator+"logs"+File.separator+"nginx.pid");
		if(file.exists()) {
		    BufferedReader reader = null;
		    try {
		        reader = new BufferedReader(new FileReader(file));
		        String tempStr;
		        while ((tempStr = reader.readLine()) != null) {
		        	if(!org.springframework.util.StringUtils.isEmpty(tempStr)) {
		        		pid = Long.parseLong(tempStr);
		        		break;
		        	}
		        }
		        reader.close();
		    } catch (IOException e) {
		        e.printStackTrace();
		    } finally {
		        if (reader != null) {
		            try {
		                reader.close();
		            } catch (IOException e1) {
		                e1.printStackTrace();
		            }
		        }
		    }
		}
		
		return pid;
	}

	/**
	 * 停止服务
	 *
	 * @return : void
	 * @Description :
	 * @Creation Date : 2018年2月2日 上午10:00:55
	 * @Author : 郭胜凯
	 */
	public Result stop() {
		Result result = null;
		
		// 是否有服务服务管理器包装
		if(serviceManager!=null) {
			// 有，先尝试由服务管理器去停止
			result = serviceManager.stop();
		}
		// 是否处理成功
		if(result==null||!result.isSuccess()) {
			// 没有，尝试直接停止
			String excuse = CMDUtil.excuse(CommonFields.NGINX + " -s stop", nginxPath);
			if (!"".equals(excuse.trim())) {
				logger.error("Nginx服务停止失败:" + excuse + ";尝试强制结束");
				SystemProcessManager.kill(CommonFields.NGINX);
				logger.info("Nginx服务已强制结束");
			}
		}		
		return result;
	}

	/**
	 * 启动服务
	 *
	 * @return : void
	 * @Description :
	 * @Creation Date : 2018年2月2日 上午10:01:02
	 * @Author : 郭胜凯
	 */
	public Result start() {
		Result result;
		
		if (isStart()) {
			throw new NginxServiceManagerException("Nginx启动时发现已有重复的服务启动。");
		}
		result = new Result();
		// 是否有服务服务管理器包装
		if(serviceManager!=null) {
			// 有，先尝试由服务管理器去启动
			result = serviceManager.start();
		}
		// 是否处理成功
		if(result==null||!result.isSuccess()) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					String excuse = CMDUtil.excuse(CommonFields.NGINX + " -c " + nginxConfigPath.replaceAll(" ", "\" \"") + "", nginxPath);
					if (!"".equals(excuse.trim())) {
						throw new NginxServiceManagerException("Nginx启动异常:" + excuse);
					}
				}
			}).start();
		}		
		
		return result; 
	}

	/**
	 * 热加载
	 *
	 * @return : void
	 * @Description :
	 * @Creation Date : 2018年2月2日 上午10:48:57
	 * @Author : 郭胜凯
	 */
	public void reload() {
		if (!isStart()) {
			throw new NginxServiceManagerException("Nginx未启动，请先启动Nginx。若您确定已在服务器上启动Nginx，那么请将本系统在sudo下执行。或手关闭Nginx并用该系统启动。");
		}
		// 校验
		checkConfiger(nginxConfigPath);
		// 重加载
		String excuse = CMDUtil.excuse(CommonFields.NGINX + " -s reload -c \"" + nginxConfigPath + "\"", nginxPath);
		if (!"".equals(excuse.trim()) && excuse.indexOf("error") != -1) {
			throw new NginxServiceManagerException("Nginx服务重加载配置失败:" + excuse);
		}
	}

	/**
	 * 读配置
	 * 
	 * @Description :
	 * @return : NgxConfig
	 * @Creation Date : 2018年2月22日 下午6:20:29
	 * @Author : 歪歪杰
	 */
	public NgxConfig read(String inEncodeing) {
		NgxConfig ngxConfig;
		
		// NgxConfig.read 始终按系统默认的编码格式进行处理，如果不是我们需要的，那就先转成默认编码格式文件给它
		String confPath = nginxConfigPath;

		// 按正确格式读取原始配置
		String confText = null;
		if(inEncodeing==null) {
			inEncodeing = encoding;
		}
		try( BufferedReader br = new BufferedReader( new InputStreamReader(new FileInputStream(nginxConfigPath), inEncodeing ))){
			StringBuilder sb = new StringBuilder();

			String line;

			while(( line = br.readLine()) != null ) {
				sb.append( line );
	
				sb.append("\r\n");

			}
			confText = sb.toString();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		String outEncoding = System.getProperty("file.encoding");
		confPath = nginxConfigPath + "."+outEncoding+".txt";
		logger.info("调试：inEncodeing="+inEncodeing+" outEncoding="+outEncoding+ " confPath="+confPath+ " confText="+confText);
		File pathFile = new File(confPath);
		pathFile.getParentFile().exists();

		try (FileOutputStream out = new FileOutputStream(pathFile)) {
			out.write(confText.getBytes(outEncoding));
			out.flush();
		} catch (Exception e) {
			throw new NginxServiceManagerException("Nginx临时配置写入配置失败:" + e.toString(), e);
		}
		
		try (InputStream stream = new FileInputStream(confPath)) {
			ngxConfig = NgxConfig.read(stream);
		} catch (IOException e) {
			throw new NginxServiceManagerException("读取Nginx配置文件失败");
		}

		return ngxConfig;
	}

	/**
	 * 读配置
	 * 
	 * @Description :
	 * @return : NgxConfig
	 * @Creation Date : 2018年2月22日 下午6:20:29
	 * @Author : 郭胜凯
	 */
	public NgxConfig read() {
		return read(null);
	}

	/**
	 * 写配置
	 * 
	 * @Description :
	 * @return : void
	 * @Creation Date : 2018年2月23日 下午7:32:26
	 * @Author : 郭胜凯
	 */
	public void save(NgxConfig conf) {
		try (FileOutputStream out = new FileOutputStream(nginxConfigPath)) {
			String s = toString(conf);
			out.write(s.getBytes(encoding));
			out.flush();
		} catch (Exception e) {
			throw new NginxServiceManagerException("Nginx配置文件写入失败:" + e.getMessage(), e);
		}
	}

	/**
	 * 配置到文本
	 * 
	 * @Description :
	 * @return : String
	 * @Creation Date : 2018年2月23日 下午7:32:38
	 * @Author : 郭胜凯
	 */
	public String toString(NgxConfig conf) {
		if (null == conf) {
			throw new NginxServiceManagerException("不能写入空配置");
		}
		NgxDumper dumper = new NgxDumper(conf);
		return dumper.dump();
	}

	/**
	 * 写配置
	 * 
	 * @Description :
	 * @return : void
	 * @Creation Date : 2018年2月23日 下午7:33:44
	 * @Author : 郭胜凯
	 */
	public void save(String conf) {
		try (FileOutputStream out = new FileOutputStream(nginxConfigPath)) {
			out.write(conf.getBytes(encoding));
			out.flush();
		} catch (Exception e) {
			throw new NginxServiceManagerException("Nginx配置文件写入失败:" + e.getMessage(), e);
		}
	}

	/**
	 * 校验Nginx配置文件
	 * 
	 * @Description :
	 * @return : void
	 * @Creation Date : 2018年2月26日 上午9:59:18
	 * @Author : 郭胜凯
	 */
	public void check(String confText) {
		String confPath = nginxConfigPath + ".temp.check";
		File pathFile = new File(confPath);
		pathFile.getParentFile().exists();

		try (FileOutputStream out = new FileOutputStream(pathFile)) {
			out.write(confText.getBytes(encoding));
			out.flush();
		} catch (Exception e) {
			throw new NginxServiceManagerException("Nginx临时配置写入配置失败:" + e.getMessage(), e);
		}

		// 校验
		try {
			checkConfiger(confPath);
		} finally {
			new File(confPath).delete();
		}
	}

	public void checkConfiger(String confPath) {
		logger.info("检查配置：["+confPath+"]");;
		String check = CMDUtil.excuse(CommonFields.NGINX + " -t -c " + confPath.replaceAll(" ", "\" \"") + "", nginxPath);
		boolean syntaxIsOk = check.indexOf(CommonFields.NGINX + ": configuration file " + confPath + " test is successful") != -1;
		if (!syntaxIsOk) {
			syntaxIsOk = check.indexOf(CommonFields.NGINX + ": the configuration file " + confPath + " syntax is ok") != -1;
		}
		if (!syntaxIsOk) {
			throw new NginxServiceManagerException("Nginx配置文件校验失败:" + check);
		}
	}

	/**
	 * 判断字符是否是中文
	 *
	 * @param c 字符
	 * @return 是否是中文
	 */
	public boolean isChinese(char c) {
		Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
		// CJK统一表意文字，CJK兼容性表意文字， CJK统一表意文字扩展A， 通用标点， CJK符号和标点符号，半角和全角表单
		if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
				|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION
				|| ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS) {
			return true;
		}
		return false;
	}

	/**
	 * 判断字符串是否是乱码
	 *
	 * @param strName 字符串
	 * @return 是否是乱码
	 */
	public boolean isMessyCode(String strName) {
		char[] ch = strName.trim().toCharArray();
		int chLength = 0;
		int count = 0;

		Map<String, Integer> mCountMap = new HashMap<String, Integer>();
		for (int i = 0; i < ch.length; i++) {
			char c = ch[i];
			Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
			String ubName = ub.toString();
			// 基本的拉丁字母不用考虑
			if (!"BASIC_LATIN".equals(ubName)) {
				// 日语片假名、平假名合并统计
				if ("KATAKANA".equals(ubName) || "HIRAGANA".equals(ubName)) {
					ubName = "JAPANESE_KANA";
				}
				Integer mCount = mCountMap.get(ubName);
				if (mCount == null) {
					mCount = 1;
				} else {
					mCount = mCount + 1;
				}
				mCountMap.put(ubName, mCount);
			}
		}
		int typeCount = mCountMap.size();
		for (Entry<String, Integer> mCount : mCountMap.entrySet()) {
			String key = mCount.getKey();
			if (!"CJK_UNIFIED_IDEOGRAPHS".equals(key)) {
				count += mCount.getValue();
			}
			chLength += mCount.getValue();
			System.out.println(mCount.getKey() + "：" + mCount.getValue());
		}

		float result = chLength == 0 ? 0 : ((0f + count) / chLength);
		logger.info("调试：typeCount: " + typeCount + "\tresult：" + result);
		// 类型多于3种或比例超过10%判定为乱码
		if (typeCount > 3 || result > 0.1) {
			return true;
		} else {
			return false;
		}
	}

	public static NginxService getServiceBy(int nginxId) {
		NginxService nginxService = null;
		
		if(nginxServiceMap!=null) {
			for(NginxService nginxService2:nginxServiceMap.values()) {
				if(nginxId==0) {
					if(nginxService2.isStart()) {
						nginxService = nginxService2;
						break;
					}
				} else {
					if(nginxService2.getId()==nginxId) {
						nginxService = nginxService2;
						break;
					}
				}
			}
		}
		
		return nginxService;
	}

	public static NginxService getInstance() {
		NginxService nginxService = null;
		if(nginxServiceMap!=null) {
			for(NginxService nginxService2:nginxServiceMap.values()) {
				nginxService = nginxService2;
				break;
			}
		}
		return nginxService;
	}

	public String getConfText() {
		return getConfText(encoding);
	}

	public String getConfText(String encodeing) {
		String confText = null;
		
		NgxConfig ngxConfig = read(encodeing);
		if (null != ngxConfig) {
			NgxDumper dumper = new NgxDumper(ngxConfig);
			confText = dumper.dump();
		}
		
		return confText;
	}

	public static void main(String[] args) throws InterruptedException {
		NginxService nginxService = NginxService.getInstance();
		System.out.println("是否有启动：" + nginxService.isStart());
		Thread.sleep(1000);
		if (!nginxService.isStart()) {
			System.out.println("开始启动");
			nginxService.start();
			Thread.sleep(1000);
			System.out.println("重新加载");
			nginxService.reload();
		} else {
			System.out.println("开始停止");
			nginxService.stop();
		}
		Thread.sleep(1000);
		System.out.println("现在状态:" + (nginxService.isStart() ? "启动" : "停止"));
	
	}

}
